1 /*
2 * Copyright (c) 2006, 2007, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package java.awt;
27
28 import java.awt.MultipleGradientPaint.CycleMethod;
29 import java.awt.MultipleGradientPaint.ColorSpaceType;
30 import java.awt.color.ColorSpace;
31 import java.awt.geom.AffineTransform;
32 import java.awt.geom.NoninvertibleTransformException;
33 import java.awt.geom.Rectangle2D;
34 import java.awt.image.ColorModel;
35 import java.awt.image.DataBuffer;
36 import java.awt.image.DataBufferInt;
37 import java.awt.image.DirectColorModel;
38 import java.awt.image.Raster;
39 import java.awt.image.SinglePixelPackedSampleModel;
40 import java.awt.image.WritableRaster;
41 import java.lang.ref.SoftReference;
42 import java.lang.ref.WeakReference;
43 import java.util.Arrays;
44
45 /**
46 * This is the superclass for all PaintContexts which use a multiple color
47 * gradient to fill in their raster. It provides the actual color
48 * interpolation functionality. Subclasses only have to deal with using
49 * the gradient to fill pixels in a raster.
50 *
51 * @author Nicholas Talian, Vincent Hardy, Jim Graham, Jerry Evans
52 */
53 abstract class MultipleGradientPaintContext implements PaintContext {
54
55 /**
56 * The PaintContext's ColorModel. This is ARGB if colors are not all
57 * opaque, otherwise it is RGB.
58 */
59 protected ColorModel model;
60
61 /** Color model used if gradient colors are all opaque. */
62 private static ColorModel xrgbmodel =
63 new DirectColorModel(24, 0x00ff0000, 0x0000ff00, 0x000000ff);
64
65 /** The cached ColorModel. */
66 protected static ColorModel cachedModel;
67
68 /** The cached raster, which is reusable among instances. */
69 protected static WeakReference<Raster> cached;
70
71 /** Raster is reused whenever possible. */
72 protected Raster saved;
73
74 /** The method to use when painting out of the gradient bounds. */
75 protected CycleMethod cycleMethod;
76
77 /** The ColorSpace in which to perform the interpolation */
78 protected ColorSpaceType colorSpace;
79
80 /** Elements of the inverse transform matrix. */
81 protected float a00, a01, a10, a11, a02, a12;
82
83 /**
84 * This boolean specifies wether we are in simple lookup mode, where an
85 * input value between 0 and 1 may be used to directly index into a single
86 * array of gradient colors. If this boolean value is false, then we have
87 * to use a 2-step process where we have to determine which gradient array
88 * we fall into, then determine the index into that array.
89 */
90 protected boolean isSimpleLookup;
91
92 /**
93 * Size of gradients array for scaling the 0-1 index when looking up
94 * colors the fast way.
95 */
96 protected int fastGradientArraySize;
97
98 /**
99 * Array which contains the interpolated color values for each interval,
100 * used by calculateSingleArrayGradient(). It is protected for possible
101 * direct access by subclasses.
102 */
103 protected int[] gradient;
104
105 /**
106 * Array of gradient arrays, one array for each interval. Used by
107 * calculateMultipleArrayGradient().
108 */
109 private int[][] gradients;
110
111 /** Normalized intervals array. */
112 private float[] normalizedIntervals;
113
114 /** Fractions array. */
115 private float[] fractions;
116
117 /** Used to determine if gradient colors are all opaque. */
118 private int transparencyTest;
119
120 /** Color space conversion lookup tables. */
121 private static final int SRGBtoLinearRGB[] = new int[256];
122 private static final int LinearRGBtoSRGB[] = new int[256];
123
124 static {
125 // build the tables
126 for (int k = 0; k < 256; k++) {
127 SRGBtoLinearRGB[k] = convertSRGBtoLinearRGB(k);
128 LinearRGBtoSRGB[k] = convertLinearRGBtoSRGB(k);
129 }
130 }
131
132 /**
133 * Constant number of max colors between any 2 arbitrary colors.
134 * Used for creating and indexing gradients arrays.
135 */
136 protected static final int GRADIENT_SIZE = 256;
137 protected static final int GRADIENT_SIZE_INDEX = GRADIENT_SIZE -1;
138
139 /**
140 * Maximum length of the fast single-array. If the estimated array size
141 * is greater than this, switch over to the slow lookup method.
142 * No particular reason for choosing this number, but it seems to provide
143 * satisfactory performance for the common case (fast lookup).
144 */
145 private static final int MAX_GRADIENT_ARRAY_SIZE = 5000;
146
147 /**
148 * Constructor for MultipleGradientPaintContext superclass.
149 */
150 protected MultipleGradientPaintContext(MultipleGradientPaint mgp,
151 ColorModel cm,
152 Rectangle deviceBounds,
153 Rectangle2D userBounds,
154 AffineTransform t,
155 RenderingHints hints,
156 float[] fractions,
157 Color[] colors,
158 CycleMethod cycleMethod,
159 ColorSpaceType colorSpace)
160 {
161 if (deviceBounds == null) {
162 throw new NullPointerException("Device bounds cannot be null");
163 }
164
165 if (userBounds == null) {
166 throw new NullPointerException("User bounds cannot be null");
167 }
168
169 if (t == null) {
170 throw new NullPointerException("Transform cannot be null");
171 }
172
173 if (hints == null) {
174 throw new NullPointerException("RenderingHints cannot be null");
175 }
176
177 // The inverse transform is needed to go from device to user space.
178 // Get all the components of the inverse transform matrix.
179 AffineTransform tInv;
180 try {
181 // the following assumes that the caller has copied the incoming
182 // transform and is not concerned about it being modified
183 t.invert();
184 tInv = t;
185 } catch (NoninvertibleTransformException e) {
186 // just use identity transform in this case; better to show
187 // (incorrect) results than to throw an exception and/or no-op
188 tInv = new AffineTransform();
189 }
190 double m[] = new double[6];
191 tInv.getMatrix(m);
192 a00 = (float)m[0];
193 a10 = (float)m[1];
194 a01 = (float)m[2];
195 a11 = (float)m[3];
196 a02 = (float)m[4];
197 a12 = (float)m[5];
198
199 // copy some flags
200 this.cycleMethod = cycleMethod;
201 this.colorSpace = colorSpace;
202
203 // we can avoid copying this array since we do not modify its values
204 this.fractions = fractions;
205
206 // note that only one of these values can ever be non-null (we either
207 // store the fast gradient array or the slow one, but never both
208 // at the same time)
209 int[] gradient =
210 (mgp.gradient != null) ? mgp.gradient.get() : null;
211 int[][] gradients =
212 (mgp.gradients != null) ? mgp.gradients.get() : null;
213
214 if (gradient == null && gradients == null) {
215 // we need to (re)create the appropriate values
216 calculateLookupData(colors);
217
218 // now cache the calculated values in the
219 // MultipleGradientPaint instance for future use
220 mgp.model = this.model;
221 mgp.normalizedIntervals = this.normalizedIntervals;
222 mgp.isSimpleLookup = this.isSimpleLookup;
223 if (isSimpleLookup) {
224 // only cache the fast array
225 mgp.fastGradientArraySize = this.fastGradientArraySize;
226 mgp.gradient = new SoftReference<int[]>(this.gradient);
227 } else {
228 // only cache the slow array
229 mgp.gradients = new SoftReference<int[][]>(this.gradients);
230 }
231 } else {
232 // use the values cached in the MultipleGradientPaint instance
233 this.model = mgp.model;
234 this.normalizedIntervals = mgp.normalizedIntervals;
235 this.isSimpleLookup = mgp.isSimpleLookup;
236 this.gradient = gradient;
237 this.fastGradientArraySize = mgp.fastGradientArraySize;
238 this.gradients = gradients;
239 }
240 }
241
242 /**
243 * This function is the meat of this class. It calculates an array of
244 * gradient colors based on an array of fractions and color values at
245 * those fractions.
246 */
247 private void calculateLookupData(Color[] colors) {
248 Color[] normalizedColors;
249 if (colorSpace == ColorSpaceType.LINEAR_RGB) {
250 // create a new colors array
251 normalizedColors = new Color[colors.length];
252 // convert the colors using the lookup table
253 for (int i = 0; i < colors.length; i++) {
254 int argb = colors[i].getRGB();
255 int a = argb >>> 24;
256 int r = SRGBtoLinearRGB[(argb >> 16) & 0xff];
257 int g = SRGBtoLinearRGB[(argb >> 8) & 0xff];
258 int b = SRGBtoLinearRGB[(argb ) & 0xff];
259 normalizedColors[i] = new Color(r, g, b, a);
260 }
261 } else {
262 // we can just use this array by reference since we do not
263 // modify its values in the case of SRGB
264 normalizedColors = colors;
265 }
266
267 // this will store the intervals (distances) between gradient stops
268 normalizedIntervals = new float[fractions.length-1];
269
270 // convert from fractions into intervals
271 for (int i = 0; i < normalizedIntervals.length; i++) {
272 // interval distance is equal to the difference in positions
273 normalizedIntervals[i] = this.fractions[i+1] - this.fractions[i];
274 }
275
276 // initialize to be fully opaque for ANDing with colors
277 transparencyTest = 0xff000000;
278
279 // array of interpolation arrays
280 gradients = new int[normalizedIntervals.length][];
281
282 // find smallest interval
283 float Imin = 1;
284 for (int i = 0; i < normalizedIntervals.length; i++) {
285 Imin = (Imin > normalizedIntervals[i]) ?
286 normalizedIntervals[i] : Imin;
287 }
288
289 // Estimate the size of the entire gradients array.
290 // This is to prevent a tiny interval from causing the size of array
291 // to explode. If the estimated size is too large, break to using
292 // separate arrays for each interval, and using an indexing scheme at
293 // look-up time.
294 int estimatedSize = 0;
295 for (int i = 0; i < normalizedIntervals.length; i++) {
296 estimatedSize += (normalizedIntervals[i]/Imin) * GRADIENT_SIZE;
297 }
298
299 if (estimatedSize > MAX_GRADIENT_ARRAY_SIZE) {
300 // slow method
301 calculateMultipleArrayGradient(normalizedColors);
302 } else {
303 // fast method
304 calculateSingleArrayGradient(normalizedColors, Imin);
305 }
306
307 // use the most "economical" model
308 if ((transparencyTest >>> 24) == 0xff) {
309 model = xrgbmodel;
310 } else {
311 model = ColorModel.getRGBdefault();
312 }
313 }
314
315 /**
316 * FAST LOOKUP METHOD
317 *
318 * This method calculates the gradient color values and places them in a
319 * single int array, gradient[]. It does this by allocating space for
320 * each interval based on its size relative to the smallest interval in
321 * the array. The smallest interval is allocated 255 interpolated values
322 * (the maximum number of unique in-between colors in a 24 bit color
323 * system), and all other intervals are allocated
324 * size = (255 * the ratio of their size to the smallest interval).
325 *
326 * This scheme expedites a speedy retrieval because the colors are
327 * distributed along the array according to their user-specified
328 * distribution. All that is needed is a relative index from 0 to 1.
329 *
330 * The only problem with this method is that the possibility exists for
331 * the array size to balloon in the case where there is a
332 * disproportionately small gradient interval. In this case the other
333 * intervals will be allocated huge space, but much of that data is
334 * redundant. We thus need to use the space conserving scheme below.
335 *
336 * @param Imin the size of the smallest interval
337 */
338 private void calculateSingleArrayGradient(Color[] colors, float Imin) {
339 // set the flag so we know later it is a simple (fast) lookup
340 isSimpleLookup = true;
341
342 // 2 colors to interpolate
343 int rgb1, rgb2;
344
345 //the eventual size of the single array
346 int gradientsTot = 1;
347
348 // for every interval (transition between 2 colors)
349 for (int i = 0; i < gradients.length; i++) {
350 // create an array whose size is based on the ratio to the
351 // smallest interval
352 int nGradients = (int)((normalizedIntervals[i]/Imin)*255f);
353 gradientsTot += nGradients;
354 gradients[i] = new int[nGradients];
355
356 // the 2 colors (keyframes) to interpolate between
357 rgb1 = colors[i].getRGB();
358 rgb2 = colors[i+1].getRGB();
359
360 // fill this array with the colors in between rgb1 and rgb2
361 interpolate(rgb1, rgb2, gradients[i]);
362
363 // if the colors are opaque, transparency should still
364 // be 0xff000000
365 transparencyTest &= rgb1;
366 transparencyTest &= rgb2;
367 }
368
369 // put all gradients in a single array
370 gradient = new int[gradientsTot];
371 int curOffset = 0;
372 for (int i = 0; i < gradients.length; i++){
373 System.arraycopy(gradients[i], 0, gradient,
374 curOffset, gradients[i].length);
375 curOffset += gradients[i].length;
376 }
377 gradient[gradient.length-1] = colors[colors.length-1].getRGB();
378
379 // if interpolation occurred in Linear RGB space, convert the
380 // gradients back to sRGB using the lookup table
381 if (colorSpace == ColorSpaceType.LINEAR_RGB) {
382 for (int i = 0; i < gradient.length; i++) {
383 gradient[i] = convertEntireColorLinearRGBtoSRGB(gradient[i]);
384 }
385 }
386
387 fastGradientArraySize = gradient.length - 1;
388 }
389
390 /**
391 * SLOW LOOKUP METHOD
392 *
393 * This method calculates the gradient color values for each interval and
394 * places each into its own 255 size array. The arrays are stored in
395 * gradients[][]. (255 is used because this is the maximum number of
396 * unique colors between 2 arbitrary colors in a 24 bit color system.)
397 *
398 * This method uses the minimum amount of space (only 255 * number of
399 * intervals), but it aggravates the lookup procedure, because now we
400 * have to find out which interval to select, then calculate the index
401 * within that interval. This causes a significant performance hit,
402 * because it requires this calculation be done for every point in
403 * the rendering loop.
404 *
405 * For those of you who are interested, this is a classic example of the
406 * time-space tradeoff.
407 */
408 private void calculateMultipleArrayGradient(Color[] colors) {
409 // set the flag so we know later it is a non-simple lookup
410 isSimpleLookup = false;
411
412 // 2 colors to interpolate
413 int rgb1, rgb2;
414
415 // for every interval (transition between 2 colors)
416 for (int i = 0; i < gradients.length; i++){
417 // create an array of the maximum theoretical size for
418 // each interval
419 gradients[i] = new int[GRADIENT_SIZE];
420
421 // get the the 2 colors
422 rgb1 = colors[i].getRGB();
423 rgb2 = colors[i+1].getRGB();
424
425 // fill this array with the colors in between rgb1 and rgb2
426 interpolate(rgb1, rgb2, gradients[i]);
427
428 // if the colors are opaque, transparency should still
429 // be 0xff000000
430 transparencyTest &= rgb1;
431 transparencyTest &= rgb2;
432 }
433
434 // if interpolation occurred in Linear RGB space, convert the
435 // gradients back to SRGB using the lookup table
436 if (colorSpace == ColorSpaceType.LINEAR_RGB) {
437 for (int j = 0; j < gradients.length; j++) {
438 for (int i = 0; i < gradients[j].length; i++) {
439 gradients[j][i] =
440 convertEntireColorLinearRGBtoSRGB(gradients[j][i]);
441 }
442 }
443 }
444 }
445
446 /**
447 * Yet another helper function. This one linearly interpolates between
448 * 2 colors, filling up the output array.
449 *
450 * @param rgb1 the start color
451 * @param rgb2 the end color
452 * @param output the output array of colors; must not be null
453 */
454 private void interpolate(int rgb1, int rgb2, int[] output) {
455 // color components
456 int a1, r1, g1, b1, da, dr, dg, db;
457
458 // step between interpolated values
459 float stepSize = 1.0f / output.length;
460
461 // extract color components from packed integer
462 a1 = (rgb1 >> 24) & 0xff;
463 r1 = (rgb1 >> 16) & 0xff;
464 g1 = (rgb1 >> 8) & 0xff;
465 b1 = (rgb1 ) & 0xff;
466
467 // calculate the total change in alpha, red, green, blue
468 da = ((rgb2 >> 24) & 0xff) - a1;
469 dr = ((rgb2 >> 16) & 0xff) - r1;
470 dg = ((rgb2 >> 8) & 0xff) - g1;
471 db = ((rgb2 ) & 0xff) - b1;
472
473 // for each step in the interval calculate the in-between color by
474 // multiplying the normalized current position by the total color
475 // change (0.5 is added to prevent truncation round-off error)
476 for (int i = 0; i < output.length; i++) {
477 output[i] =
478 (((int) ((a1 + i * da * stepSize) + 0.5) << 24)) |
479 (((int) ((r1 + i * dr * stepSize) + 0.5) << 16)) |
480 (((int) ((g1 + i * dg * stepSize) + 0.5) << 8)) |
481 (((int) ((b1 + i * db * stepSize) + 0.5) ));
482 }
483 }
484
485 /**
486 * Yet another helper function. This one extracts the color components
487 * of an integer RGB triple, converts them from LinearRGB to SRGB, then
488 * recompacts them into an int.
489 */
490 private int convertEntireColorLinearRGBtoSRGB(int rgb) {
491 // color components
492 int a1, r1, g1, b1;
493
494 // extract red, green, blue components
495 a1 = (rgb >> 24) & 0xff;
496 r1 = (rgb >> 16) & 0xff;
497 g1 = (rgb >> 8) & 0xff;
498 b1 = (rgb ) & 0xff;
499
500 // use the lookup table
501 r1 = LinearRGBtoSRGB[r1];
502 g1 = LinearRGBtoSRGB[g1];
503 b1 = LinearRGBtoSRGB[b1];
504
505 // re-compact the components
506 return ((a1 << 24) |
507 (r1 << 16) |
508 (g1 << 8) |
509 (b1 ));
510 }
511
512 /**
513 * Helper function to index into the gradients array. This is necessary
514 * because each interval has an array of colors with uniform size 255.
515 * However, the color intervals are not necessarily of uniform length, so
516 * a conversion is required.
517 *
518 * @param position the unmanipulated position, which will be mapped
519 * into the range 0 to 1
520 * @returns integer color to display
521 */
522 protected final int indexIntoGradientsArrays(float position) {
523 // first, manipulate position value depending on the cycle method
524 if (cycleMethod == CycleMethod.NO_CYCLE) {
525 if (position > 1) {
526 // upper bound is 1
527 position = 1;
528 } else if (position < 0) {
529 // lower bound is 0
530 position = 0;
531 }
532 } else if (cycleMethod == CycleMethod.REPEAT) {
533 // get the fractional part
534 // (modulo behavior discards integer component)
535 position = position - (int)position;
536
537 //position should now be between -1 and 1
538 if (position < 0) {
539 // force it to be in the range 0-1
540 position = position + 1;
541 }
542 } else { // cycleMethod == CycleMethod.REFLECT
543 if (position < 0) {
544 // take absolute value
545 position = -position;
546 }
547
548 // get the integer part
549 int part = (int)position;
550
551 // get the fractional part
552 position = position - part;
553
554 if ((part & 1) == 1) {
555 // integer part is odd, get reflected color instead
556 position = 1 - position;
557 }
558 }
559
560 // now, get the color based on this 0-1 position...
561
562 if (isSimpleLookup) {
563 // easy to compute: just scale index by array size
564 return gradient[(int)(position * fastGradientArraySize)];
565 } else {
566 // more complicated computation, to save space
567
568 // for all the gradient interval arrays
569 for (int i = 0; i < gradients.length; i++) {
570 if (position < fractions[i+1]) {
571 // this is the array we want
572 float delta = position - fractions[i];
573
574 // this is the interval we want
575 int index = (int)((delta / normalizedIntervals[i])
576 * (GRADIENT_SIZE_INDEX));
577
578 return gradients[i][index];
579 }
580 }
581 }
582
583 return gradients[gradients.length - 1][GRADIENT_SIZE_INDEX];
584 }
585
586 /**
587 * Helper function to convert a color component in sRGB space to linear
588 * RGB space. Used to build a static lookup table.
589 */
590 private static int convertSRGBtoLinearRGB(int color) {
591 float input, output;
592
593 input = color / 255.0f;
594 if (input <= 0.04045f) {
595 output = input / 12.92f;
596 } else {
597 output = (float)Math.pow((input + 0.055) / 1.055, 2.4);
598 }
599
600 return Math.round(output * 255.0f);
601 }
602
603 /**
604 * Helper function to convert a color component in linear RGB space to
605 * SRGB space. Used to build a static lookup table.
606 */
607 private static int convertLinearRGBtoSRGB(int color) {
608 float input, output;
609
610 input = color/255.0f;
611 if (input <= 0.0031308) {
612 output = input * 12.92f;
613 } else {
614 output = (1.055f *
615 ((float) Math.pow(input, (1.0 / 2.4)))) - 0.055f;
616 }
617
618 return Math.round(output * 255.0f);
619 }
620
621 /**
622 * {@inheritDoc}
623 */
624 public final Raster getRaster(int x, int y, int w, int h) {
625 // If working raster is big enough, reuse it. Otherwise,
626 // build a large enough new one.
627 Raster raster = saved;
628 if (raster == null ||
629 raster.getWidth() < w || raster.getHeight() < h)
630 {
631 raster = getCachedRaster(model, w, h);
632 saved = raster;
633 }
634
635 // Access raster internal int array. Because we use a DirectColorModel,
636 // we know the DataBuffer is of type DataBufferInt and the SampleModel
637 // is SinglePixelPackedSampleModel.
638 // Adjust for initial offset in DataBuffer and also for the scanline
639 // stride.
640 // These calls make the DataBuffer non-acceleratable, but the
641 // Raster is never Stable long enough to accelerate anyway...
642 DataBufferInt rasterDB = (DataBufferInt)raster.getDataBuffer();
643 int[] pixels = rasterDB.getData(0);
644 int off = rasterDB.getOffset();
645 int scanlineStride = ((SinglePixelPackedSampleModel)
646 raster.getSampleModel()).getScanlineStride();
647 int adjust = scanlineStride - w;
648
649 fillRaster(pixels, off, adjust, x, y, w, h); // delegate to subclass
650
651 return raster;
652 }
653
654 protected abstract void fillRaster(int pixels[], int off, int adjust,
655 int x, int y, int w, int h);
656
657
658 /**
659 * Took this cacheRaster code from GradientPaint. It appears to recycle
660 * rasters for use by any other instance, as long as they are sufficiently
661 * large.
662 */
663 private static synchronized Raster getCachedRaster(ColorModel cm,
664 int w, int h)
665 {
666 if (cm == cachedModel) {
667 if (cached != null) {
668 Raster ras = (Raster) cached.get();
669 if (ras != null &&
670 ras.getWidth() >= w &&
671 ras.getHeight() >= h)
672 {
673 cached = null;
674 return ras;
675 }
676 }
677 }
678 return cm.createCompatibleWritableRaster(w, h);
679 }
680
681 /**
682 * Took this cacheRaster code from GradientPaint. It appears to recycle
683 * rasters for use by any other instance, as long as they are sufficiently
684 * large.
685 */
686 private static synchronized void putCachedRaster(ColorModel cm,
687 Raster ras)
688 {
689 if (cached != null) {
690 Raster cras = (Raster) cached.get();
691 if (cras != null) {
692 int cw = cras.getWidth();
693 int ch = cras.getHeight();
694 int iw = ras.getWidth();
695 int ih = ras.getHeight();
696 if (cw >= iw && ch >= ih) {
697 return;
698 }
699 if (cw * ch >= iw * ih) {
700 return;
701 }
702 }
703 }
704 cachedModel = cm;
705 cached = new WeakReference<Raster>(ras);
706 }
707
708 /**
709 * {@inheritDoc}
710 */
711 public final void dispose() {
712 if (saved != null) {
713 putCachedRaster(model, saved);
714 saved = null;
715 }
716 }
717
718 /**
719 * {@inheritDoc}
720 */
721 public final ColorModel getColorModel() {
722 return model;
723 }
724 }